package com.ruoyi.spot.openapi.lastesttrade.controller;

import java.sql.Timestamp;
import java.text.MessageFormat;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import javax.servlet.http.HttpServletResponse;

import com.alibaba.fastjson2.JSONArray;
import com.ruoyi.spot.constants.SpotMonitorConstants;
import com.ruoyi.spot.openapi.lastesttrade.domain.SpotOpenapiLastestTradation;
import com.ruoyi.spot.openapi.lastesttrade.domain.VO.SpotOpenapiLastestTradationVO;
import com.ruoyi.util.LarkDangerRobotUtils;
import com.ruoyi.util.LarkRecoverRobotUtils;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.spot.openapi.lastesttrade.service.ISpotOpenapiLastestTradationService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.page.TableDataInfo;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;

@RestController
@RequestMapping("/sp_api_lastest_tradation/sp_api_lastest_tradation_bak")
public class SpotOpenapiLastestTradationController extends BaseController {

    private static final Logger log = LoggerFactory.getLogger(SpotOpenapiLastestTradationController.class);

    private static final long MAX_DELAY_TOLERANCE_MS = 10 * 60 * 1000; // 10分钟

    @Autowired
    private ISpotOpenapiLastestTradationService spotOpenapiLastestTradationService;

    @Value("${my.lark.dangerlark}")
    private String dangerlark;

    @Value("${my.lark.recoverlark}")
    private String recoverlark;


    @PreAuthorize("@ss.hasPermi('sp_api_lastest_tradation:sp_api_lastest_tradation_bak:list')")
    @GetMapping("/list")
    public TableDataInfo list(SpotOpenapiLastestTradationVO spotOpenapiLastestTradation) {
        startPage();
        List<SpotOpenapiLastestTradationVO> list = spotOpenapiLastestTradationService.selectSpotOpenapiLastestTradationList(spotOpenapiLastestTradation);
        return getDataTable(list);
    }

    @PreAuthorize("@ss.hasPermi('sp_api_lastest_tradation:sp_api_lastest_tradation_bak:export')")
    @Log(title = "最近成交", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, SpotOpenapiLastestTradationVO spotOpenapiLastestTradation) {
        List<SpotOpenapiLastestTradationVO> list = spotOpenapiLastestTradationService.selectSpotOpenapiLastestTradationList(spotOpenapiLastestTradation);
        ExcelUtil<SpotOpenapiLastestTradationVO> util = new ExcelUtil<SpotOpenapiLastestTradationVO>(SpotOpenapiLastestTradationVO.class);
        util.exportExcel(response, list, "最近成交数据");
    }

    private static String checkoutMessage(String openapiMessage) {
        try {
            JSONArray jsonArray;
            String trim = openapiMessage != null ? openapiMessage.trim() : "";
            if (trim.startsWith("[")) {
                jsonArray = JSON.parseArray(openapiMessage);
            } else if (trim.startsWith("{")) {
                JSONObject obj = JSON.parseObject(openapiMessage);
                Object data = obj.get("data");
                if (data instanceof JSONArray) {
                    jsonArray = (JSONArray) data;
                } else if (data instanceof JSONObject) {
                    JSONArray rows = ((JSONObject) data).getJSONArray("rows");
                    jsonArray = rows != null ? rows : new JSONArray();
                } else {
                    JSONArray arr = obj.getJSONArray("result");
                    if (arr == null) arr = obj.getJSONArray("records");
                    jsonArray = arr != null ? arr : new JSONArray();
                }
            } else {
                return "false, 接口返回非法格式";
            }

            if (jsonArray == null || jsonArray.isEmpty()) {
                return "false, server未推送数据";
            }

            StringBuilder errors = new StringBuilder();

            // =======================[ 1. (新) 数据新鲜度校验 ]=======================
            // 遍历整个列表以确保找到真正的最大时间戳，无论列表是升序、降序还是混乱的
            long maxTimestamp = 0L;
            for (Object obj : jsonArray) {
                JSONObject trade = (JSONObject) obj;
                // 提前校验结构，防止后续NPE
                boolean hasTime = trade.containsKey("time") || trade.containsKey("t");
                if (!hasTime) {
                    errors.append("返回值结构体错误，缺少time或t字段; ");
                    // 发现致命结构错误，可提前退出
                    return "false, " + errors.toString().trim();
                }
                long currentTimestamp = trade.containsKey("time") ? trade.getLongValue("time") : trade.getLongValue("t");
                if (currentTimestamp > maxTimestamp) {
                    maxTimestamp = currentTimestamp;
                }
            }

            // 用找到的最大时间戳进行校验
            if (maxTimestamp > 0) {
                long currentTimeMs = System.currentTimeMillis();
                if (currentTimeMs - maxTimestamp > MAX_DELAY_TOLERANCE_MS) {
                    errors.append("10min内无成交; ");
                }
            }
            // =======================[ 新鲜度校验结束 ]=========================


            // =======================[ 2. 字段结构校验 ]=========================
            for (Object obj : jsonArray) {
                JSONObject jsonObject = (JSONObject) obj;
                boolean oldFmt = jsonObject.containsKey("side") && jsonObject.containsKey("price")
                        && jsonObject.containsKey("qty") && jsonObject.containsKey("time");
                boolean newFmt = jsonObject.containsKey("p") && jsonObject.containsKey("q")
                        && jsonObject.containsKey("t") && jsonObject.containsKey("ibm");
                if (!oldFmt && !newFmt) {
                    errors.append("返回值结构体错误，缺少必要字段; ");
                    break;
                }
            }

            // =======================[ 3. 时间戳连续性校验 ]=======================
            // 校验整个列表的排序趋势是否一致 (全部递增或全部递减)
            if (jsonArray.size() > 1) {
                int trend = 0; // 0: 未定, 1: 递减, -1: 递增
                for (int i = 0; i < jsonArray.size() - 1; i++) {
                    JSONObject cur = jsonArray.getJSONObject(i);
                    JSONObject nxt = jsonArray.getJSONObject(i + 1);
                    long currentTime = cur.containsKey("time") ? cur.getLongValue("time") : cur.getLongValue("t");
                    long nextTime = nxt.containsKey("time") ? nxt.getLongValue("time") : nxt.getLongValue("t");

                    if (currentTime == nextTime) {
                        continue; // 时间戳相同，跳过，继续寻找趋势
                    }

                    if (trend == 0) { // 首次确定趋势
                        trend = (currentTime > nextTime) ? 1 : -1;
                    } else { // 趋势已定，进行校验
                        boolean isTrendBroken;
                        if (trend == 1) { // 期望递减
                            isTrendBroken = currentTime < nextTime;
                        } else { // 期望递增
                            isTrendBroken = currentTime > nextTime;
                        }

                        if (isTrendBroken) {
                            errors.append("时间戳不连续 (排序混乱); ");
                            break; // 发现错误，立即中断
                        }
                    }
                }
            }

            // =======================[ 4. side字段值校验 ]=======================
            for (Object obj : jsonArray) {
                JSONObject jsonObject = (JSONObject) obj;
                String sideVal;
                if (jsonObject.containsKey("side")) {
                    sideVal = jsonObject.getString("side");
                } else {
                    boolean ibm = jsonObject.getBooleanValue("ibm");
                    sideVal = ibm ? "sell" : "buy";
                }
                if (!"buy".equals(sideVal) && !"sell".equals(sideVal)) {
                    errors.append("side/ibm字段值错误; ");
                    break;
                }
            }

            if (errors.length() > 0) {
                // 使用.toString().trim()去除可能的尾部空格
                return "false, " + errors.toString().trim();
            }

            return "true";
        } catch (Exception e) {
            log.error("🚨 [Trades] checkoutMessage 发生异常", e);
            return "false, JSON解析或校验时异常: " + e.getMessage();
        }
    }

    public void SpotOpenapiLastestTradation() {
        List<SpotOpenapiLastestTradationVO> spotOpenAPIList = spotOpenapiLastestTradationService.selectSpotAllData();
        List<Integer> dangerProductionInspections = new ArrayList<>();
        List<Integer> recoverProductionInspections = new ArrayList<>();
        for (SpotOpenapiLastestTradationVO spotOpenapiLastestTradationVO : spotOpenAPIList) {
            Integer id = spotOpenapiLastestTradationVO.getId();
            String currencyPair = spotOpenapiLastestTradationVO.getCurrencyPair();
            String callingState = spotOpenapiLastestTradationVO.getCallingState();
            Integer successTimes = spotOpenapiLastestTradationVO.getSuccessTimes();
            Integer failTimes = spotOpenapiLastestTradationVO.getFailTimes();
            Integer dangerWarning = spotOpenapiLastestTradationVO.getDangerWarning();
            Integer safeWarning = spotOpenapiLastestTradationVO.getSafeWarning();
            String openapirequest = null;
            OkHttpClient client = new OkHttpClient().newBuilder().build();
            try {
                String url = MessageFormat.format(SpotMonitorConstants.SPOT_OPENAPI_LASTESTTRADATION, currencyPair, 10);
                log.info("📤 [Trades] 请求: symbol={}, limit={}, url={}", currencyPair, 10, url);

                Request request = new Request.Builder()
                        .url(url)
                        .get()
                        .build();
                Response response = client.newCall(request).execute();
                int httpCode = response.code();
                String responseBody = response.body().string();
                String preview = responseBody == null ? "" : responseBody;
                if (preview.length() > 400) preview = preview.substring(0, 400) + "...";
                log.info("📥 [Trades] 响应: code={}, length={}, preview={}", httpCode, responseBody == null ? 0 : responseBody.length(), preview);

                openapirequest = checkoutMessage(responseBody);
                log.info("✅ [Trades] 校验结果: symbol={}, result={}", currencyPair, openapirequest);
            } catch (Exception e) {
                log.error("🚨 [Trades] 调用异常: symbol={}, error={}", currencyPair, e.getMessage(), e);
            }

            Timestamp now = new Timestamp(System.currentTimeMillis());

            if (openapirequest == null || openapirequest.isEmpty()) {
                if (Objects.isNull(callingState) || callingState.isEmpty()) {
                    dangerProductionInspections.add(id);
                    successTimes = 0;
                    failTimes = 1;
                    Integer currencysId = spotOpenapiLastestTradationService.queryCurryNumber(currencyPair);
                    SpotOpenapiLastestTradation o1 = new SpotOpenapiLastestTradation(id, currencysId, "off_line", now, successTimes, failTimes, "Server未推送Kline数据", null, null, null);
                    spotOpenapiLastestTradationService.insertSpotOpenapiLastestTradation(o1);
                    log.info("[现货-OpenaAPI-LastestTradation-1}：【" + currencyPair + "】首次调用即失败");
                } else if (callingState.equals("off_line")) {
                    if (dangerWarning == null || dangerWarning == 1) {
                        dangerProductionInspections.add(id);
                    }
                    failTimes = failTimes + 1;
                    SpotOpenapiLastestTradation o1 = new SpotOpenapiLastestTradation(id, null, callingState, now, successTimes, failTimes, "Server未推送Kline数据", null, null, null);
                    spotOpenapiLastestTradationService.updateSpotOpenapiLastestTradation(o1);
                    log.info("[现货-OpenaAPI-LastestTradation-2}：【" + currencyPair + "】订阅一直失败");
                } else if (callingState.equals("on_line")) {
                    dangerProductionInspections.add(id);
                    callingState = "off_line";
                    failTimes = failTimes + 1;
                    SpotOpenapiLastestTradation o1 = new SpotOpenapiLastestTradation(id, null, callingState, now, successTimes, failTimes, "Server未推送Kline数据", null, null, null);
                    spotOpenapiLastestTradationService.updateSpotOpenapiLastestTradation(o1);
                    log.info("[现货-OpenaAPI-LastestTradation-3}：【" + currencyPair + "】订阅首次失败");
                } else {
                    log.info("[现货-OpenaAPI-LastestTradation-4}：【" + currencyPair + "】未知情况");
                }
            } else if (openapirequest.startsWith("false")) {
                String reason = openapirequest.split(",")[1];
                if (Objects.isNull(callingState) || callingState.isEmpty()) {
                    dangerProductionInspections.add(id);
                    successTimes = 0;
                    failTimes = 1;
                    Integer currencysId = spotOpenapiLastestTradationService.queryCurryNumber(currencyPair);
                    SpotOpenapiLastestTradation o1 = new SpotOpenapiLastestTradation(id, currencysId, "off_line", now, successTimes, failTimes, reason, null, null, null);
                    spotOpenapiLastestTradationService.insertSpotOpenapiLastestTradation(o1);
                    log.info("[现货-OpenaAPI-LastestTradation-5}：【" + currencyPair + "】" + openapirequest);
                } else if (callingState.equals("off_line")) {
                    if (dangerWarning == null || dangerWarning == 1) {
                        dangerProductionInspections.add(id);
                    }
                    failTimes = failTimes + 1;
                    SpotOpenapiLastestTradation o1 = new SpotOpenapiLastestTradation(id, null, callingState, now, successTimes, failTimes, reason, null, null, null);
                    spotOpenapiLastestTradationService.updateSpotOpenapiLastestTradation(o1);
                    log.info("[现货-OpenaAPI-LastestTradation-6}：【" + currencyPair + "】" + openapirequest);
                } else if (callingState.equals("on_line")) {
                    dangerProductionInspections.add(id);
                    callingState = "off_line";
                    failTimes = failTimes + 1;
                    SpotOpenapiLastestTradation o1 = new SpotOpenapiLastestTradation(id, null, callingState, now, successTimes, failTimes, reason, null, null, null);
                    spotOpenapiLastestTradationService.updateSpotOpenapiLastestTradation(o1);
                    log.info("[现货-OpenaAPI-LastestTradation-7}：【" + currencyPair + "】" + openapirequest);
                } else {
                    log.info("[现货-OpenaAPI-LastestTradation-8}：【" + currencyPair + "】" + openapirequest);
                }
            } else if (openapirequest.equals("true")) {
                if (Objects.isNull(callingState) || callingState.isEmpty()) {
                    successTimes = 1;
                    failTimes = 0;
                    Integer currencysId = spotOpenapiLastestTradationService.queryCurryNumber(currencyPair);
                    SpotOpenapiLastestTradation o1 = new SpotOpenapiLastestTradation(id, currencysId, "on_line", now, successTimes, failTimes, null, null, null, null);
                    spotOpenapiLastestTradationService.insertSpotOpenapiLastestTradation(o1);
                    log.info("[现货-OpenaAPI-LastestTradation-9}：【" + currencyPair + "】" + openapirequest);
                } else if (callingState.equals("on_line")) {
                    successTimes = successTimes + 1;
                    SpotOpenapiLastestTradation o1 = new SpotOpenapiLastestTradation(id, null, callingState, now, successTimes, failTimes, null, null, null, null);
                    spotOpenapiLastestTradationService.updateSpotOpenapiLastestTradation(o1);
                    log.info("[现货-OpenaAPI-LastestTradation-10}：【" + currencyPair + "】" + openapirequest);
                } else if (callingState.equals("off_line")) {
                    if (safeWarning == null || safeWarning == 1) {
                        recoverProductionInspections.add(id);
                    }
                    callingState = "on_line";
                    successTimes = successTimes + 1;
                    SpotOpenapiLastestTradation o1 = new SpotOpenapiLastestTradation(id, null, callingState, now, successTimes, failTimes, null, null, null, null);
                    spotOpenapiLastestTradationService.updateSpotOpenapiLastestTradation(o1);
                    log.info("[现货-OpenaAPI-LastestTradation-11}：【" + currencyPair + "】" + openapirequest);
                } else {
                    log.info("[现货-OpenaAPI-LastestTradation-12}：【" + currencyPair + "】" + openapirequest);
                }
            }
        }

        List<SpotOpenapiLastestTradationVO> spotKlineCurrencysList_new = spotOpenapiLastestTradationService.selectSpotAllData();
        List<SpotOpenapiLastestTradationVO> allProductionInspections2 = new ArrayList<>();
        List<SpotOpenapiLastestTradationVO> allProductionInspections3 = new ArrayList<>();
        for (SpotOpenapiLastestTradationVO vo : spotKlineCurrencysList_new) {
            Integer id = vo.getId();
            if (dangerProductionInspections.contains(id)) {
                allProductionInspections2.add(vo);
            } else if (recoverProductionInspections.contains(id)) {
                allProductionInspections3.add(vo);
            }
        }

        // 🔧 修复时区偏差：使用系统时区确保时间准确，但保持JST标签显示习惯
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss '(系统时间)'");
        ZoneId zoneId = ZoneId.systemDefault(); // 使用系统默认时区，与数据库记录时间保持一致

        if (!allProductionInspections2.isEmpty()) {
            String dangernotice;
            if (allProductionInspections2.size() == 1) {
                SpotOpenapiLastestTradationVO item = allProductionInspections2.get(0);
                String name = item.getCurrencyPair();
                Date time = item.getLastExecutionTime();
                String reason = item.getLastFailureReason();
                String formattedDate = "";
                if (time != null) {
                    ZonedDateTime zdt = time.toInstant().atZone(zoneId);
                    formattedDate = zdt.format(formatter);
                }
                String DangerLarkNotice = "【现货-LastestTradation-OpenaAPI】下方为行情最近成交故障的交易对名称及调用时间，请您关注！\n"
                        + "交易对名称： " + name + "   " + "故障时间： " + formattedDate + "    故障原因： " + reason + "   " + "\n";
                dangernotice = LarkDangerRobotUtils.notice(DangerLarkNotice, dangerlark);
                log.info("LARK_DANGER_RESULT[TRADE][single]: {}", dangernotice);
            } else {
                StringBuilder noticeBuilder = new StringBuilder("【现货-LastestTradation-OpenaAPI】下方为行情最近成交故障的交易对名称及调用时间，请您关注！\n");
                for (SpotOpenapiLastestTradationVO item : allProductionInspections2) {
                    String name = item.getCurrencyPair();
                    Date time = item.getLastExecutionTime();
                    String reason = item.getLastFailureReason();
                    String formattedDate = "";
                    if (time != null) {
                        ZonedDateTime zdt = time.toInstant().atZone(zoneId);
                        formattedDate = zdt.format(formatter);
                    }
                    noticeBuilder.append("交易对名称： ").append(name).append("   ")
                            .append("故障时间： ").append(formattedDate).append("    ")
                            .append("故障原因： ").append(reason).append("   \n");
                }
                dangernotice = LarkDangerRobotUtils.notice(noticeBuilder.toString(), dangerlark);
                log.info("LARK_DANGER_RESULT[TRADE][multi]: {} size={}", dangernotice, allProductionInspections2.size());
            }

            if (dangernotice.equals("lark_success")) {
                for (SpotOpenapiLastestTradationVO item : allProductionInspections2) {
                    Integer id = item.getId();
                    SpotOpenapiLastestTradation o2 = new SpotOpenapiLastestTradation(id, null, null, null, null, null, null, 0, null, "Lark发送故障X信息情况成功！");
                    spotOpenapiLastestTradationService.updateSpotOpenapiLastestTradation(o2);
                }
            } else {
                for (SpotOpenapiLastestTradationVO item : allProductionInspections2) {
                    Integer id = item.getId();
                    SpotOpenapiLastestTradation o2 = new SpotOpenapiLastestTradation(id, null, null, null, null, null, null, 1, null, "Lark发送故障X信息情况失败！");
                    spotOpenapiLastestTradationService.updateSpotOpenapiLastestTradation(o2);
                }
            }
        }

        if (!allProductionInspections3.isEmpty()) {
            StringBuilder noticeBuilder = new StringBuilder("【现货-LastestTradation-OpenaAPI】下方为行情最近成交恢复的交易对名称及调用时间，请您关注！\n");
            for (SpotOpenapiLastestTradationVO item : allProductionInspections3) {
                String name = item.getCurrencyPair();
                Date time = item.getLastExecutionTime();
                String formattedDate = "";
                if (time != null) {
                    ZonedDateTime zdt = time.toInstant().atZone(zoneId);
                    formattedDate = zdt.format(formatter);
                }
                noticeBuilder.append("交易对名称： ").append(name).append("   ")
                        .append("恢复时间： ").append(formattedDate).append("   \n");
            }
            String recovernotice = LarkRecoverRobotUtils.notice(noticeBuilder.toString(), recoverlark);
            log.info("LARK_RECOVER_RESULT[TRADE]: {} size={}", recovernotice, allProductionInspections3.size());

            if (recovernotice.equals("lark_success")) {
                for (SpotOpenapiLastestTradationVO item : allProductionInspections3) {
                    Integer id = item.getId();
                    SpotOpenapiLastestTradation o2 = new SpotOpenapiLastestTradation(id, null, null, null, null, null, null, null, 0, "Lark发送故障V信息情况成功！");
                    spotOpenapiLastestTradationService.updateSpotOpenapiLastestTradation(o2);
                }
            } else {
                for (SpotOpenapiLastestTradationVO item : allProductionInspections3) {
                    Integer id = item.getId();
                    SpotOpenapiLastestTradation o2 = new SpotOpenapiLastestTradation(id, null, null, null, null, null, null, null, 1, "Lark发送故障V信息情况失败！");
                    spotOpenapiLastestTradationService.updateSpotOpenapiLastestTradation(o2);
                }
            }
        }
        log.info("============【【现货-OpenaAPI-LastestTradation】执行完成============");
    }
}